﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace NTL.Assignment
{
    /// <summary>
    /// FileWriter employs a writer thread to consume items in the blocking queue.
    /// FileWriter writes duplicate file name pairs (blocking queue items) to output file.
    /// </summary>
    public class FileWriter
    {
        /// <summary>
        /// Consume thread.
        /// </summary>
        private Thread writerThread;

        /// <summary>
        /// Output file writer.
        /// </summary>
        private StreamWriter writer;

        /// <summary>
        /// Event to synchroinze pause\resume calls.
        /// </summary>
        private ManualResetEvent pauseResumeEvent = new ManualResetEvent(true);

        /// <summary>
        /// Event to synchronize start\abort\restart calls.
        /// </summary>
        private ManualResetEvent abortEvent = new ManualResetEvent(false);

        /// <summary>
        /// Blocking queue. The writer thread will consume items of this blocking queue.
        /// </summary>
        private ConcurrentQueue<IList<string>> duplicatePairs;

        /// <summary>
        /// Initializes a new instance of the <see cref="FileWriter"/> class.
        /// </summary>
        /// <param name="outputFileFullPath">Full path to the output file including the file name.</param>
        public FileWriter(ConcurrentQueue<IList<string>> duplicates, string outputFileFullPath)
        {
            this.duplicatePairs = duplicates;
            this.OutputFileFullPath = outputFileFullPath; 
        }

        /// <summary>
        /// Gets of sets the output file full path.
        /// </summary>
        public string OutputFileFullPath { get; set; }

        /// <summary>
        /// Number of duplicate pairs written to file.
        /// </summary>
        public int Count { get; private set; }

        /// <summary>
        /// Starts consumer thread. This is asynchronous.
        /// </summary>
        public void Write()
        {
            // Initialize output file.
            writer = new StreamWriter(OutputFileFullPath, false);

            // Reset counter
            Count = 0;

            // Create new thread passing DoWrite delegate.
            writerThread = new Thread(DoWrite);

            writerThread.Start();
        }

        /// <summary>
        /// Pauses the file writer.
        /// </summary>
        public void Pause()
        {
            // Pausing writer thread.
            pauseResumeEvent.Reset();
        }

        /// <summary>
        /// Resumes writer thread had it been paused.
        /// </summary>
        public void Resume()
        {
            // Resuming writer thread.
            pauseResumeEvent.Set();
        }

        /// <summary>
        /// Aborts the file writer thread. This is a synchronous call.
        /// The writer thread will flush existing items in the queue.
        /// </summary>
        public void Abort()
        {
            pauseResumeEvent.Set();

            abortEvent.Set();

            writerThread.Join(TimeSpan.FromMilliseconds(500));
        }

        /// <summary>
        /// Restarts writing thread. Note that this call is not a resume,
        /// but a restart of the output writer thread.
        /// </summary>
        public void Restart()
        {
            // Reset abort event.
            abortEvent.Reset();

            Write();
        }

        /// <summary>
        /// Do the actual writing
        /// </summary>
        private void DoWrite()
        {
            while (true)
            {
                pauseResumeEvent.WaitOne();

                IList<string> duplicatePair = new List<string>();
                duplicatePairs.TryDequeue(out duplicatePair);

                if (duplicatePair != null)
                {
                    writer.WriteLine(duplicatePair[0] + ", " + duplicatePair[1]);
                    writer.Flush();
                    Count++;
                }

                if (abortEvent.WaitOne(0))
                {
                    try
                    {
                        // Close the stream, since we are aborting.
                        writer.Close();
                    }
                    catch 
                    {  /* swallow */ }

                    break;
                }
            }
        }
    }
}
